﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace LoadTestDbToXml
{
    internal class XmlGenerator
    {
        private readonly XElement xml;
        private readonly XElement resultsXml;
        private readonly XElement messagesXml;
        private readonly XElement transactionsXml;
        private readonly XElement countersXml;

        public XmlGenerator(LoadTestRun lastTestRun, IGrouping<int, LoadTestTransactionResults2>[] prevTransactionGroupes, List<LoadTestRun> loadTestRuns)
        {
            xml = new XElement("LoadTestCustomResults");
            xml.Add(
                new XAttribute("LoadTestRunId", lastTestRun.LoadTestRunId),
                new XAttribute("Name", lastTestRun.LoadTestName),
                new XAttribute("Outcome", lastTestRun.Outcome),
                new XAttribute("Duration", TimeSpan.FromSeconds(lastTestRun.RunDuration).TotalMinutes),
                new XAttribute("Description", lastTestRun.Description),
                new XAttribute("Comment", ""));

            const string prefix = "Run #";
            var prevRunTitles = GetPrevRunsTitles(
                prevTransactionGroupes, prefix, loadTestRuns);

            transactionsXml =
                new XElement("Transactions",
                             new XAttribute("CompTitile1", prevRunTitles[0]),
                             new XAttribute("CompTitile2", prevRunTitles[1]),
                             new XAttribute("CompTitile3", prevRunTitles[2]),
                             new XAttribute("CompTitile4", prevRunTitles[3]),
                             new XAttribute("CompTitile5", prevRunTitles[4])
                    );

            xml.Add(transactionsXml);

            countersXml = new XElement("Counters");
            xml.Add(countersXml);

            resultsXml = new XElement("Results");
            xml.Add(resultsXml);

            messagesXml = new XElement("Exceptions");
            xml.Add(messagesXml);

        }

        public string Output
        {
            get { return xml.ToString(); }
        }

        public void AddResult(TestResult testResult)
        {
            var element =
                new XElement("Result",
                             new XAttribute("TestCaseName", testResult.TestCaseName),
                             new XAttribute("TestsRun", testResult.TestsRun),
                             new XAttribute("PassedTests", testResult.PassedTests),
                             new XAttribute("FailedTests", testResult.FailedTests),
                             new XAttribute("PrecentageFailures", testResult.PrecentageFailures),
                             new XAttribute("Minimum", testResult.Minimum),
                             new XAttribute("Average", testResult.Average),
                             new XAttribute("Maximum", testResult.Maximum));

            resultsXml.Add(element);
        }

        public void AddException(LoadTestMessageSummary messageSummary)
        {
            var element =
                new XElement("Exception",
                             new XAttribute("TestCaseName", messageSummary.TestCaseName),
                             new XAttribute("MessageTimeStamp", messageSummary.MessageTimeStamp.ToString(@"hh:mm:ss.ff")),
                             new XAttribute("MessageText", messageSummary.MessageText),
                             new XAttribute("StackTrace", messageSummary.StackTrace));

            messagesXml.Add(element);

        }

        public void Save(string xmlFileName)
        {
            xml.Save(xmlFileName);
        }

        public void AddTransaction(LoadTestTransactionResults2 transaction,
                                   IGrouping<int, LoadTestTransactionResults2>[] prevTransactions)
        {
            var prevRunComprations = GetPrevRunsComprations(transaction, prevTransactions);
            Debug.Assert(transaction != null, "transaction != null");
            Debug.Assert(prevRunComprations != null, "prevRunComprations != null");

            var prevRunCompration1 = prevRunComprations[0];
            var prevRunCompration2 = prevRunComprations[1];
            var prevRunCompration3 = prevRunComprations[2];
            var prevRunCompration4 = prevRunComprations[3];
            var prevRunCompration5 = prevRunComprations[4];
            Debug.Assert(prevRunCompration1 != null, "prevRunCompration1 != null");
            Debug.Assert(prevRunCompration2 != null, "prevRunCompration2 != null");
            Debug.Assert(prevRunCompration3 != null, "prevRunCompration3 != null");
            Debug.Assert(prevRunCompration4 != null, "prevRunCompration4 != null");
            Debug.Assert(prevRunCompration5 != null, "prevRunCompration5 != null");

            var perc90 = transaction.Percentile90.HasValue ? transaction.Percentile90.Value.ToString("0.###") : "N/A";
            var perc95 = transaction.Percentile95.HasValue ? transaction.Percentile95.Value.ToString("0.###") : "N/A";
            var perc99 = transaction.Percentile99.HasValue ? transaction.Percentile99.Value.ToString("0.###") : "N/A";

            var element = new XElement("Transaction",
                                       new XAttribute("Name", transaction.TransactionName),
                                       new XAttribute("Runs", transaction.TransactionCount),
                                       new XAttribute("Minimum", transaction.Minimum.ToString("0.###")),
                                       new XAttribute("Average", transaction.Average.ToString("0.###")),
                                       new XAttribute("Maximum", transaction.Maximum.ToString("0.###")),
                                       new XAttribute("Percentile90", perc90),
                                       new XAttribute("Percentile95", perc95),
                                       new XAttribute("Percentile99", perc99),
                                       new XAttribute("CompValue1", prevRunCompration1.ComprationString),
                                       new XAttribute("CompValue2", prevRunCompration2.ComprationString),
                                       new XAttribute("CompValue3", prevRunCompration3.ComprationString),
                                       new XAttribute("CompValue4", prevRunCompration4.ComprationString),
                                       new XAttribute("CompValue5", prevRunCompration5.ComprationString),
                                       new XAttribute("CompStatus1", prevRunCompration1.Status),
                                       new XAttribute("CompStatus2", prevRunCompration2.Status),
                                       new XAttribute("CompStatus3", prevRunCompration3.Status),
                                       new XAttribute("CompStatus4", prevRunCompration4.Status),
                                       new XAttribute("CompStatus5", prevRunCompration5.Status)
                );
            transactionsXml.Add(element);

        }

        private static string[] GetPrevRunsTitles(IGrouping<int, LoadTestTransactionResults2>[] prevTransactionsGrouped,
                                                  string prefix, List<LoadTestRun> loadTestRuns)
        {
            string[] prevRunTitles = new string[5];

            for (int i = 0; i < prevRunTitles.Length; i++)
            {
                if (prevTransactionsGrouped.Length > i)
                {
                    var prevTransactions = prevTransactionsGrouped[i];
                    var runId = prevTransactions.Key;
                    var title = prefix + runId;
                    var loadTestRun = loadTestRuns.Single(run => run.LoadTestRunId == runId);

                    title = string.Format("{0:d/M/yyyy HH:mm:ss} {1}", loadTestRun.StartTime, loadTestRun.Description);
                    prevRunTitles[i] = title;
                }
                else
                {
                    prevRunTitles[i] = "N/A";
                }
            }
            return prevRunTitles;
        }

        private static ComprationItem[] GetPrevRunsComprations(LoadTestTransactionResults2 transaction,
                                                               IGrouping<int, LoadTestTransactionResults2>[]
                                                                   prevTransactions)
        {
            ComprationItem[] prevRunComprations = new ComprationItem[5];

            for (int i = 0; i < prevRunComprations.Length; i++)
            {
                prevRunComprations[i] = ComprationItem.Empty;
                if (prevTransactions.Length <= i) continue;

                var transactionName = transaction.TransactionName;

                var prevTransaction = prevTransactions[i].FirstOrDefault(
                    result => result.TransactionName == transactionName);

                if (prevTransaction == null) continue;

                double current;
                double prev;

                if (transaction.Percentile90.HasValue && prevTransaction.Percentile90.HasValue)
                {
                    current = transaction.Percentile90.Value;
                    prev = prevTransaction.Percentile90.Value;

                }
                else
                {
                    current = transaction.Average;
                    prev = prevTransaction.Average;
                }

                double percentage;
                if (prev > current)
                {
                    percentage = ((prev / current * 100) - 100);
                }
                else
                {
                    percentage = ((current/prev*100)-100) * -1;
                }
                
                var compration = string.Format("{0:0.###}({1:0.#}%)", prev, percentage);
                bool status = percentage >= 0;
                prevRunComprations[i] = new ComprationItem(compration, status);
            }
            return prevRunComprations;
        }

        public void AddCounterGroup(CounterGroup counterGroup, IEnumerable<LoadTestComputedCounterSample> counterSamples)
        {
            var groupElement = new XElement("Group",
                new XAttribute("Name", counterGroup.Name),
                new XAttribute("CountersCount", counterGroup.Counters.Count));
            
            var counterNamesElement = new XElement("CounterNames");
            
            foreach (Counter counter in counterGroup.Counters)
            {
                var counterRepresentation = 
                    string.Format("{0} ({1})", counter.CounterName, counter.InstanceName);
                
                var counterNameElement = new XElement("CounterName", counterRepresentation);
                counterNamesElement.Add(counterNameElement);
            }
            groupElement.Add(counterNamesElement);

            foreach (var machineName in counterGroup.Machines)
            {
                var machineElement = new XElement("Machine",
                    new XAttribute("Name", machineName));

                foreach (Counter counter in counterGroup.Counters)
                {
                    Counter counter1 = counter;
                    string machineName1 = machineName;

                    IEnumerable<LoadTestComputedCounterSample> samples = GetSamples(counterSamples, machineName1, counter1);
                    Dictionary<CounterdResultsKey, CounterdResultsValue> aggregatedResults = AggregatedResults(samples);

                    foreach (var aggregatedResult in aggregatedResults)
                    {
                        CounterdResultsKey counterDetails = aggregatedResult.Key;
                        CounterdResultsValue resultsValue = aggregatedResult.Value;
                        {
                            var counterElement =
                                new XElement("Counter", resultsValue.Value,
                                             new XAttribute("Avg", resultsValue.Avg),
                                             new XAttribute("CounterName", counterDetails.CounterName),
                                             new XAttribute("InstanceName", counterDetails.InstanceName));

                            machineElement.Add(counterElement);
                        }
                    }
                }
                groupElement.Add(machineElement);
            }
            countersXml.Add(groupElement);
        }

        private static Dictionary<CounterdResultsKey, CounterdResultsValue> AggregatedResults(IEnumerable<LoadTestComputedCounterSample> samples)
        {
            var aggregatedResults = new Dictionary<CounterdResultsKey, CounterdResultsValue>();
            foreach (var sample in samples)
            {
                var key = new CounterdResultsKey(sample.CounterName, sample.InstanceName);
                CounterdResultsValue results;
                var exist = aggregatedResults.TryGetValue(key, out results);
                if (!exist)
                {
                    results = new CounterdResultsValue();
                    aggregatedResults.Add(key, results);
                }
                float value = sample.ComputedValue.HasValue ? sample.ComputedValue.Value : 0;
                results.Add(value);
            }
            
            return aggregatedResults;
        }

        private static IEnumerable<LoadTestComputedCounterSample> GetSamples(IEnumerable<LoadTestComputedCounterSample> counterSamples, string machineName1, Counter counter1)
        {
            var samples = counterSamples.Where(
                sample =>
                    {
                        bool b = sample.MachineName.ToLower() == machineName1.ToLower() &&
                                 (string.IsNullOrEmpty(counter1.InstanceName) ||
                                  sample.InstanceName.ToLower() == counter1.InstanceName.ToLower()) &&
                                 (string.IsNullOrEmpty(counter1.CounterName) ||
                                  sample.CounterName.ToLower() == counter1.CounterName.ToLower());

                        return b;
                    });
            return samples;
        }

        class CounterdResultsValue
        {
            private readonly StringBuilder builder = new StringBuilder();
            private float sum;
            private int count;

            public string Value
            {
                get { return builder.ToString(); }
            }

            public float Avg
            {
                get { return sum/count; }
            }

            public void Add(float value)
            {
                count++;
                sum += value;
                builder.Append(string.Format("{0:0.###}", value) + ";");

            }
        }

        private class CounterdResultsKey
        {
            private readonly string counterName;
            private readonly string instanceName;

            public CounterdResultsKey(string counterName, string instanceName)
            {
                this.counterName = counterName;
                this.instanceName = instanceName;
            }

            public string InstanceName
            {
                get { return instanceName; }
            }

            public string CounterName
            {
                get { return counterName; }
            }

            private bool Equals(CounterdResultsKey other)
            {
                if (ReferenceEquals(null, other)) return false;
                if (ReferenceEquals(this, other)) return true;
                return Equals(other.counterName, counterName) && Equals(other.instanceName, instanceName);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != typeof (CounterdResultsKey)) return false;
                return Equals((CounterdResultsKey) obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    return ((counterName != null ? counterName.GetHashCode() : 0)*397) ^
                           (instanceName != null ? instanceName.GetHashCode() : 0);
                }
            }
        }

        private class ComprationItem
        {
            private readonly string comprationString;
            private readonly bool status;

            private static readonly ComprationItem s_empty = new ComprationItem("N/A", true);

            public ComprationItem(string comprationString, bool status)
            {
                this.comprationString = comprationString;
                this.status = status;
            }

            public string ComprationString
            {
                get { return comprationString; }
            }

            public bool Status
            {
                get { return status; }
            }

            public static ComprationItem Empty
            {
                get { return s_empty; }
            }
        }
    }
}
